﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using log4net;
using System.Reflection;
using System.IO;
using System.Web;
using log4net.Repository.Hierarchy;
using log4net.Appender;
using System.Globalization;
using System.Diagnostics;

namespace PBDesk.Framework.Log4NetWrapper
{
	public delegate void LogEventHandler(object sender, LogEventArgs e);
	public delegate void MoreInfoLogEventHandler(object sender, MoreInformationEventArgs e);
	public class LogMgr
	{
		#region Private Fields
		
		private static LogMgr current = null;

		/// <summary>
		/// Collect the events.
		/// </summary>
		private ArrayList requestMoreInformationEvents;

		/// <summary>
		/// The actual connection to LOG4NET.
		/// </summary>
		private ILog logImplementation = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

		/// <summary>
		/// By using this flag, we avoid stack overflow inside the called
		/// event handlers during logging.
		/// </summary>
		private bool isInsideLogging = false;

		/// <summary>
		/// The path where the configuration is read from.
		/// This value is set upon a call to ConfigureLogging().
		/// </summary>
		private string configurationFilePath;


		#endregion

		#region Events Declaration

		public event LogEventHandler LogEvent = null;

		public event MoreInfoLogEventHandler CollectMoreInformation
		{
			add
			{
				if (requestMoreInformationEvents == null)
				{
					requestMoreInformationEvents = new ArrayList();
				}
				requestMoreInformationEvents.Add(value);
			}

			remove
			{
				if (requestMoreInformationEvents != null)
				{
					if (requestMoreInformationEvents.IndexOf(value) >= 0)
					{
						requestMoreInformationEvents.Remove(value);
					}
				}
			}
		}

		#endregion

		#region Constructor

		public LogMgr()
		{
			this.CollectMoreInformation += new MoreInfoLogEventHandler(Logger_CollectMoreInformation_Http);
			this.CollectMoreInformation += new MoreInfoLogEventHandler(Logger_CollectMoreInformation_NetworkEnv);
			this.CollectMoreInformation += new MoreInfoLogEventHandler(Logger_CollectMoreInformation_Environmental);
			this.CollectMoreInformation += new MoreInfoLogEventHandler(Logger_CollectMoreInformation_Assembly);
			this.CollectMoreInformation += new MoreInfoLogEventHandler(Logger_CollectMoreInformation_Custom);
		}

		#endregion

		#region Static Member

		public static LogMgr Current
		{
			get
			{
				lock (typeof(LogMgr))
				{
					if (current == null)
					{
						current = new LogMgr();
						current.ConfigureLogging();
					}
				}
				return current;
			}
		}

		#endregion

		#region Reading Log4net XML configuration
		/// <summary>
		/// Automatically configures logging.
		/// This function is called automatically, too.
		/// </summary>
		public void ConfigureLogging()
		{
			lock (this)
			{
				bool configured = false;

				// --

				// Tells the logging system the correct path.
				Assembly a = Assembly.GetEntryAssembly();

				if (a != null && a.Location != null)
				{
					string path = a.Location + ".config";

					if (File.Exists(path))
					{
						log4net.Config.XmlConfigurator.Configure(new FileInfo(path));
						configurationFilePath = path;
						configured = true;
					}
					else
					{
						path = FindConfigInPath(Path.GetDirectoryName(a.Location));
						if (File.Exists(path))
						{
							log4net.Config.XmlConfigurator.Configure(new FileInfo(path));
							configurationFilePath = path;
							configured = true;
						}
					}
				}

				// --

				// Also, try web.config.
				if (!configured)
				{
					if (HttpContext.Current != null &&
						HttpContext.Current.Server != null &&
						HttpContext.Current.Request != null)
					{
						string path = HttpContext.Current.Server.MapPath(
							HttpContext.Current.Request.ApplicationPath);

						path = path.TrimEnd('\\') + "\\Web.config";

						if (File.Exists(path))
						{
							log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo(path));
							configurationFilePath = path;
							configured = true;
						}
					}
				}

				// --

				if (!configured)
				{
					// Tells the logging system the correct path.
					a = Assembly.GetExecutingAssembly();

					if (a != null && a.Location != null)
					{
						string path = a.Location + ".config";

						if (File.Exists(path))
						{
							log4net.Config.XmlConfigurator.Configure(new FileInfo(path));
							configurationFilePath = path;
							configured = true;
						}
						else
						{
							path = FindConfigInPath(Path.GetDirectoryName(a.Location));
							if (File.Exists(path))
							{
								log4net.Config.XmlConfigurator.Configure(new FileInfo(path));
								configurationFilePath = path;
								configured = true;
							}
						}
					}
				}

				// --

				if (!configured)
				{
					// Tells the logging system the correct path.
					a = Assembly.GetCallingAssembly();

					if (a != null && a.Location != null)
					{
						string path = a.Location + ".config";

						if (File.Exists(path))
						{
							log4net.Config.XmlConfigurator.Configure(new FileInfo(path));
							configurationFilePath = path;
							configured = true;
						}
						else
						{
							path = FindConfigInPath(Path.GetDirectoryName(a.Location));
							if (File.Exists(path))
							{
								log4net.Config.XmlConfigurator.Configure(new FileInfo(path));
								configurationFilePath = path;
								configured = true;
							}
						}
					}
				}

				// --

				if (!configured)
				{
					// Look in the path where this library is stored.
					a = Assembly.GetAssembly(typeof(LogMgr));

					if (a != null && a.Location != null)
					{
						string path = FindConfigInPath(Path.GetDirectoryName(a.Location));
						if (File.Exists(path))
						{
							log4net.Config.XmlConfigurator.Configure(new FileInfo(path));
							configurationFilePath = path;
							configured = true;
						}
					}
				}

				// --
				// Make some changes to the appenders.

				if (configured)
				{
					// Find all file appenders and make all 
					// relative paths as absolute paths.

					Logger logger = logImplementation.Logger as Logger;

					if (logger != null)
					{
						if (logger.Hierarchy.Root.Appenders != null)
						{
							foreach (IAppender appender in logger.Hierarchy.Root.Appenders)
							{
								FileAppender fileAppender = appender as FileAppender;

								if (fileAppender != null)
								{
									// If a relative path is specified,
									// make absolute.
									if (!Path.IsPathRooted(fileAppender.File))
									{
										/*
										string path = 
											Path.GetFullPath(
											Path.Combine( 
											Path.GetDirectoryName( configurationFilePath ),
											fileAppender.File ) );

										fileAppender.File = path;
										*/
									}
								}
							}
						}
					}
				}
			}
		}

		/// <summary>
		/// Searches for a configuration file in the given path.
		/// </summary>
		private string FindConfigInPath(string path)
		{
			string[] files = Directory.GetFiles(path);

			if (files != null && files.Length > 0)
			{
				foreach (string file in files)
				{
					if (Path.GetExtension(file).Trim('.').ToLower(
						CultureInfo.CurrentCulture) == "config")
					{
						return file;
					}
				}
			}

			// Not found.
			return string.Empty;
		}

		#endregion

		#region Logging Methods
		// <summary>
		/// If called from within a web context, this returns non-null.
		/// </summary>
		private System.Web.TraceContext WebTrace
		{
			get
			{
				if (System.Web.HttpContext.Current == null)
				{
					return null;
				}
				else
				{
					return System.Web.HttpContext.Current.Trace;
				}
			}
		}

		public void LogBase(LogType type, object message, Exception t)
		{
			bool locIsInsideLogging = this.isInsideLogging;
			this.isInsideLogging = true;
			string logTypeStr = type.ToString();
			try
			{
				if (!locIsInsideLogging)
				{
					message = DispatchRequestMoreInformationEvents(type,message, t);
				}

				switch (type)
				{
					case LogType.Debug: logImplementation.Debug(message, t); break;
					case LogType.Info: logImplementation.Info(message, t); break;
					case LogType.Warn: logImplementation.Warn(message, t); break;
					case LogType.Error: logImplementation.Error(message, t); break;
					case LogType.Fatal: logImplementation.Fatal(message, t); break;
				}

				logImplementation.Fatal(message, t);

				if (WebTrace != null)
				{
					WebTrace.Write(logTypeStr, message == null ? string.Empty : message.ToString(), t);
				}

				Trace.WriteLine(MakeTraceMessage(message, t), logTypeStr);

				if (!locIsInsideLogging && LogEvent != null)
				{
					LogEvent(this, new LogEventArgs(type, MakeTraceMessage(message, t), t));
				}
			}
			finally
			{
				this.isInsideLogging = false;
			}

		}

		public void LogDebug(object message)
		{
			LogDebug(message, null);
		}

		public void LogDebug(object message, Exception t)
		{
			LogBase(LogType.Debug, message, t);
		}


		public void LogInfo(object message)
		{
			LogInfo(message, null);
		}

		public void LogInfo(object message, Exception t)
		{
			LogBase(LogType.Info, message, t);
		}

		public void LogWarn(object message)
		{
			LogWarn(message, null);
		}

		public void LogWarn(object message, Exception t)
		{
			LogBase(LogType.Warn, message, t);
		}

		public void LogError(object message)
		{
			LogError(message, null);
		}

		public void LogError(object message, Exception t)
		{
			LogBase(LogType.Error, message, t);
		}

		public void LogFatal(object message)
		{
			LogFatal(message, null);
		}

		public void LogFatal(object message, Exception t)
		{
			LogBase(LogType.Fatal, message, t);
		}

		#endregion

		#region Private Helpers

		#region Indent Methods
		/// <summary>
		/// Indents block of lines.
		/// </summary>
		/// <param name="textToIndent">The textToIndent to indent.</param>
		/// <returns>Returns the indented textToIndent.</returns>
		public static string Indent(string textToIndent)
		{
			return Indent(textToIndent, "    ");
		}

		/// <summary>
		/// Indents block of lines.
		/// </summary>
		/// <param name="textToIndent">The textToIndent to indent.</param>
		/// <param name="linePrefix">The prefix to add before every found line.</param>
		/// <returns>Returns the indented textToIndent.</returns>
		public static string Indent(string textToIndent, string linePrefix)
		{
			if (textToIndent == null)
			{
				return textToIndent;
			}
			else
			{
				textToIndent = textToIndent.Replace("\r\n", "\n");
				textToIndent = textToIndent.Replace("\r", "\n");

				if (textToIndent.IndexOf('\n') < 0)
				{
					return linePrefix + textToIndent;
				}
				else
				{
					string[] lines = textToIndent.Split('\n');

					string result = string.Empty;

					foreach (string line in lines)
					{
						if (result.Length > 0)
						{
							result += "\r\n";
						}

						result += linePrefix + line;
					}

					return result;
				}
			}
		}
		
		#endregion

		/// <summary>
		/// Combines the more information strings and the regular
		/// message string.
		/// </summary>
		private object CombineMoreInformationMessage(object message, string moreInformationMessage)
		{
			if (moreInformationMessage == null)
			{
				return message;
			}
			else
			{
				if (message == null)
				{
					return Indent(moreInformationMessage);
				}
				else
				{
					return message.ToString().TrimEnd() 
									+ "\r\n\r\n" 
									+ Indent(moreInformationMessage.Trim());
				}
			}
		}

		/// <summary>
		/// Dispatches the RequestMoreInformation event to
		/// all connected event handlers (if any).
		/// </summary>
		private object DispatchRequestMoreInformationEvents(LogType type, object message)
		{
			return DispatchRequestMoreInformationEvents( type, message, null);
		}

		/// <summary>
		/// Dispatches the RequestMoreInformation event to
		/// all connected event handlers (if any).
		/// </summary>
		private object DispatchRequestMoreInformationEvents(LogType type, object message, Exception t)
		{
			if (requestMoreInformationEvents == null ||
				requestMoreInformationEvents.Count <= 0)
			{
				return message;
			}
			else
			{
				object cumulatedMessage = message;

				foreach (MoreInfoLogEventHandler evt in requestMoreInformationEvents)
				{
					if (evt != null)
					{
						MoreInformationEventArgs args = new MoreInformationEventArgs(type,message,t);

						evt(this, args);

						// Yes, add additional information.
						cumulatedMessage = CombineMoreInformationMessage(cumulatedMessage,args.MoreInformation);
					}
				}

				return cumulatedMessage;
			}
		}

		#region Mark Trace Messages
		
		/// <summary>
		/// Make a string to trace from a given message.
		/// </summary>
		private static string MakeTraceMessage(object message)
		{
			return message == null ? string.Empty : message.ToString();
		}

		/// <summary>
		/// Make a string to trace from a given message and exception.
		/// </summary>
		public static string MakeTraceMessage(Exception t)
		{
			return MakeTraceMessage(null, t);
		}

		/// <summary>
		/// Make a string to trace from a given message and exception.
		/// </summary>
		public static string MakeTraceMessage(object message, Exception t)
		{
			if (message == null)
			{
				if (t == null)
				{
					return string.Empty;
				}
				else
				{
					return string.Format(CultureInfo.CurrentCulture,
						"{0}\r\n\r\n-----------------\r\n\r\n{1}\r\n\r\n-----------------\r\n\r\n{2}",
						t.GetType() == null || t.GetType().FullName == null ? string.Empty : t.GetType().FullName,
						t.Message == null ? string.Empty : t.Message.Trim(),
						t.StackTrace == null ? string.Empty : t.StackTrace.Trim());
				}
			}
			else
			{
				if (t == null)
				{
					return message.ToString();
				}
				else
				{
					// Comment.
					return string.Format(CultureInfo.CurrentCulture,
						"{0}\r\n\r\n{1}\r\n\r\n-----------------\r\n\r\n{2}\r\n\r\n-----------------\r\n\r\n{3}",
						message,
						t.GetType() == null || t.GetType().FullName == null ? string.Empty : t.GetType().FullName,
						t.Message == null ? string.Empty : t.Message.Trim(),
						t.StackTrace == null ? string.Empty : t.StackTrace.Trim());
				}
			}
		}

		#endregion
		#endregion

		#region Event Methods

		public void Logger_CollectMoreInformation_Http(object sender, MoreInformationEventArgs e)
		{

		}
		public void Logger_CollectMoreInformation_NetworkEnv(object sender, MoreInformationEventArgs e)
		{

		}
		public void Logger_CollectMoreInformation_Environmental(object sender, MoreInformationEventArgs e)
		{

		}
		public void Logger_CollectMoreInformation_Assembly(object sender, MoreInformationEventArgs e)
		{

		}
		public void Logger_CollectMoreInformation_Custom(object sender, MoreInformationEventArgs e)
		{

		}
		#endregion
	}
}
